iT邦幫忙

2025 iThome 鐵人賽

DAY 6
1

短暫的中場休息 Q&A 練習後,開始進入第三個篇章 —— Resource Management。這次的 5 個知識點還算直觀,所以我們稍微加速一次講完,為後續更具挑戰的內容做好準備!

13. Use objects to manage resources

在經過前幾個章節的淬鍊後,大家都知曉清理資源的重要性。然而直接使用 delete 並不安全!如同下方的範例,若中途 return 或發生 exception,資源可能無法正常釋放而導致洩漏:

void f()
{
    Investment *pInv = createInvestment();
    ...
    delete pInv;
}

為了解決這個問題,我們應把刪除 object 的操作放到它的 destructor,這樣只要程式離開 f() 就會被自動清除。可以使用智慧指標 auto_ptr,destructor 會自主對其指向的 object 呼叫 delete

void f()
{
    std::auto_ptr<Investment> pInv(createInvestment());
    ...
}

說到智慧指標,就順勢介紹兩種設計原則:

  • Resource Acquisition Is Initialization (RAII):資源的取得與釋放應由物件的生命週期自動管理。 e.g. auto_ptr
  • Resource Copy Safety Principle (RCSP):較現代的用法,確保在資源實施複製共享時安全地管理。
    • std::unique_ptr:確保資源的「唯一所有權」,禁止複製只允許 std::move 轉移。
    • std::shared_ptr:用於多個物件共享同一資源,透過「引用計數」自動管理資源的生命週期。

14. Think carefully about copying behavior in resource-managing classes.

複製 RAII 物件時需要複製其管理的資源。針對 resource-managing class 的複製行為,有以下幾種可能的方式:

  • 禁止複製:確保同一資源不會被多個 resource-managing class 繼續管理。就像同一位 Yoyo 不可能同時入職多家公司。
  • 引用計數:shared_ptr允許多個 RAII 類共享一個資源。注意 shared_ptr 默認在引用計數歸零後會釋放資源,但對於像 mutex 鎖這種需要特別操作的資源,需要初始化時傳入自定義的刪除函式來正確釋放資源。
  • 完整複製底層資源:內存支持 deep copy,能安全地複製這些資源。
  • 移交所有權:透過 std::move 等語意確保資源的管理權在不同物件間正確轉移。

15. Provide access to raw resources in resource-managing classes

為了應對大量可能需要調用的 API,使用者必須能獲取 resource-managing class 管理的原始資源 —— 例如指標或物件。

  • 顯式轉換:提供明確的 interface 讓使用者直接訪問所管理的資源。 e.g. get()、重載 *-> 運算子
  • 隱式轉換:允許資源管理物件自動轉換為其管理的資源,在調用 API 時可以像直接處理資源一樣處理資源管理物件。需謹慎使用。

Resource-managing class 的目標是管理資源的獲取和釋放,所以無需考量直接訪問原始資源是否會破壞其封裝性。前面提到的 RAII 設計原則,就是為了確保釋放資源等操作會確實執行。

16. Use the same form in corresponding uses of new and delete

new 會配置一塊空間並調用 constructor,而 delete 則會調用 destructor 並釋放內存。當創建物件陣列時,編譯器會記錄 array 的長度,並保存在此記憶體的前部位置。釋放時需用 delete [] 明確表示釋放的是 array,否則可能會被錯誤地視為單一 object。

因此 newdelete 應配套使用,避免未定義行為。如果在 new 表達式中使用 [] 創建陣列,那麼在對應的 delete 表達式中也必須使用 []

17. Store newed objects in smart pointers in standalone statements

即使使用智慧指標,若程式碼沒做好保護,在某些情況下仍可能有內存洩漏的風險:

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

在上述程式碼中,編譯器會先對函式參數進行求值,但並未規定具體順序。這可能導致 new Widget 先被執行,而在處理 priority() 時拋出 exception。此時智慧指標的構造函數尚未調用,導致內存洩漏。

想避免這個問題,可先在獨立語句中保存智慧指標,然後再將其傳入函式:

std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

上一篇
[Day 5] 中場休息 Q&A小測驗!!
下一篇
[Day 7] Designs and Declarations I
系列文
30 天 Effective C++ 大挑戰!!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言